Code
# Run to list and update librairies
# # list des librairies utilisées et leurs versions
# !pixi list --explicit
# # vérifie que les librairies sont à jour
# !pixi updateBut de la mission : déterminer si les données sur l’éducation de la banque mondiale peut informer les décisions d’ouverture vers de nouveaux pays
Détails de la mission :
Les données de la Banque mondiale sont disponibles à l’adresse suivante : https://datacatalog.worldbank.org/dataset/education-statistics. L’organisme “EdStats All Indicator Query” de la Banque mondiale répertorie 4000 indicateurs internationaux décrivant l’accès à l’éducation, l’obtention de diplômes et des informations relatives aux professeurs, aux dépenses liées à l’éducation… Plus d’info sur ce site : http://datatopics.worldbank.org/education/.
EdStats, OpenClassrooms, academy
Academy, start-up EdTech, propose des formations en ligne pour des publics de niveau lycée et université. Dans le cadre d’une expansion internationale, l’entreprise explore les opportunités sur de nouveaux marchés grâce à des données globales sur l’éducation issues de la Banque mondiale (EdStats All Indicator Query), qui regroupent plus de 4000 indicateurs relatifs à l’accès à l’éducation, les dépenses, et les résultats académiques.
Cette analyse exploratoire a pour but de :
J’utilise Pixi pour gérer les librairies et leurs versions. Le fichier pyproject.toml contient les librairies utilisées et leurs versions. Pour créer un environnement virtuel avec Pixi, il faut créer un dossier pour le projet, copier le fichier pyproject.toml dans ce dossier, puis l’initialiser avec la commande pixi init.
# Run to list and update librairies
# # list des librairies utilisées et leurs versions
# !pixi list --explicit
# # vérifie que les librairies sont à jour
# !pixi updatefrom pathlib import Path
import pandas as pd
from IPython.display import Markdown
from itables import show
import itables.options as opt
import missingno as msno
import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib.ticker as mticker
import geopandas as gpd
import numpy as np
figsize = plt.rcParams["figure.figsize"]
# Afficher écriture scientifique des valeurs numériques
pd.set_option("display.float_format", "{:g}".format)
opt.scrollY = "200px"
opt.scrollCollapse = True
opt.paging = False
opt.column_filters = "header"dataset_folder = Path("data/Dataset_Edstats_csv/")
csv_files = list(dataset_folder.glob("*.csv"))
print(f"Fichiers CSV trouvés : {[file.name for file in csv_files]}")Fichiers CSV trouvés : ['EdStatsCountry.csv', 'EdStatsCountry-Series.csv', 'EdStatsData.csv', 'EdStatsSeries.csv', 'EdStatsFootNote.csv']
dfs = {file.stem: pd.read_csv(file) for file in csv_files}Présentation du contenu des fichiers répartis par onglets (50 premières lignes)
| Country Code | Short Name | Table Name | Long Name | 2-alpha code | Currency Unit | Special Notes | Region | Income Group | WB-2 code | National accounts base year | National accounts reference year | SNA price valuation | Lending category | Other groups | System of National Accounts | Alternative conversion factor | PPP survey year | Balance of Payments Manual in use | External debt Reporting status | System of trade | Government Accounting concept | IMF data dissemination standard | Latest population census | Latest household survey | Source of most recent Income and expenditure data | Vital registration complete | Latest agricultural census | Latest industrial data | Latest trade data | Latest water withdrawal data | Unnamed: 31 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
| CountryCode | SeriesCode | DESCRIPTION | Unnamed: 3 |
|---|---|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
| Country Name | Country Code | Indicator Name | Indicator Code | 1970 | 1971 | 1972 | 1973 | 1974 | 1975 | 1976 | 1977 | 1978 | 1979 | 1980 | 1981 | 1982 | 1983 | 1984 | 1985 | 1986 | 1987 | 1988 | 1989 | 1990 | 1991 | 1992 | 1993 | 1994 | 1995 | 1996 | 1997 | 1998 | 1999 | 2000 | 2001 | 2002 | 2003 | 2004 | 2005 | 2006 | 2007 | 2008 | 2009 | 2010 | 2011 | 2012 | 2013 | 2014 | 2015 | 2016 | 2017 | 2020 | 2025 | 2030 | 2035 | 2040 | 2045 | 2050 | 2055 | 2060 | 2065 | 2070 | 2075 | 2080 | 2085 | 2090 | 2095 | 2100 | Unnamed: 69 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
| Series Code | Topic | Indicator Name | Short definition | Long definition | Unit of measure | Periodicity | Base Period | Other notes | Aggregation method | Limitations and exceptions | Notes from original source | General comments | Source | Statistical concept and methodology | Development relevance | Related source links | Other web links | Related indicators | License Type | Unnamed: 20 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
| CountryCode | SeriesCode | Year | DESCRIPTION | Unnamed: 4 |
|---|---|---|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
Conversion des types pour chaque dataframe. Permet d’optimiser les ressources et réduire la zone mémoire utilisée.
def convert_columns_types(df_data: pd.DataFrame) -> pd.DataFrame:
df_data = df_data.convert_dtypes()
fcols = df_data.select_dtypes("float").columns
icols = df_data.select_dtypes("integer").columns
df_data[fcols] = df_data[fcols].apply(pd.to_numeric, downcast="float")
df_data[icols] = df_data[icols].apply(pd.to_numeric, downcast="integer")
return df_datacols = ["Country Name", "Country Code","Indicator Name", "Indicator Code"]
dfs["EdStatsData"][cols] = dfs["EdStatsData"][cols].astype("category")Affichage des types de données de chaque dataframe par colonnes.
| None | 0 |
|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
| None | 0 |
|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
| None | 0 |
|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
| None | 0 |
|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
| None | 0 |
|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
# Liste pour stocker les informations de chaque dataframe
summary_data = []
for name, df in dfs.items():
summary_data.append({
"Fichier": name,
"Dimensions": df.shape,
"Noms colonnes": df.columns.to_list(),
"Types de données": df.dtypes.to_dict(),
})
# Création d'un DataFrame pour afficher ces informations
summary_df = pd.DataFrame(summary_data)
# Affichage du tableau
show(summary_df)| Fichier | Dimensions | Noms colonnes | Types de données |
|---|---|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
EdStatsCountry contient des informations sur la géolocalisation des pays (dans quelle partie du monde ils se situent), informations sur la date du recensement de la population et la monnaie utilisée.
➡️ Je vais conserver les colonne ["Country Code", "Short Name", "Currency Unit", "Region"] Ces colonnes peuvent m’être utile pour grouper mes données
EdStatsData est le fichier le plus pertinent pour mon analyse.
↪️ Pour la suite je vais maintenant analyser le fichier EdStatsData
Je commence par analyser la colonne Country Code car il me semble qu’il y a trop de pays.
Puis les indicateurs pour mon analyse (colonne Indicator Name).
Enfin je conserverai uniquement les colonnes des années qui contiennent le moins de données manquantes.
EdStatsDataChargement du fichier EdStatsData et description du contenu de ses colonnes.
edStatsData = dfs["EdStatsData"]
show(edStatsData.describe(), "Description des données de EdStatsData")| None | 1970 | 1971 | 1972 | 1973 | 1974 | 1975 | 1976 | 1977 | 1978 | 1979 | 1980 | 1981 | 1982 | 1983 | 1984 | 1985 | 1986 | 1987 | 1988 | 1989 | 1990 | 1991 | 1992 | 1993 | 1994 | 1995 | 1996 | 1997 | 1998 | 1999 | 2000 | 2001 | 2002 | 2003 | 2004 | 2005 | 2006 | 2007 | 2008 | 2009 | 2010 | 2011 | 2012 | 2013 | 2014 | 2015 | 2016 | 2017 | 2020 | 2025 | 2030 | 2035 | 2040 | 2045 | 2050 | 2055 | 2060 | 2065 | 2070 | 2075 | 2080 | 2085 | 2090 | 2095 | 2100 | Unnamed: 69 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
# Affichage graphique des valeurs manquantes
msno.bar(
edStatsData,
figsize=figsize,
fontsize=6,
)
plt.title("Valeurs manquantes dans EdStatsData")Text(0.5, 1.0, 'Valeurs manquantes dans EdStatsData')
On peut voir avec le graphique ci dessus qu’il y a beaucoup de valeurs manquantes. Voyons voir maintenant leurs répartitions:
show(edStatsData.isnull().mean() *100,"Répartition des valeurs manquantes dans EdStatsData")| None | 0 |
|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
# Créer un DataFrame à partir du nombre de valeurs manquantes par colonne
mean_missing_data = edStatsData.isnull().mean() * 100
mean_missing_data = pd.DataFrame(mean_missing_data, columns=["Pourcentage_manquant"])
# Ajouter la colonne Acceptable
mean_missing_data["Manquants < 90%"] = mean_missing_data["Pourcentage_manquant"] < 90
# Réinitialiser l'index pour avoir une meilleure visualisation
mean_missing_data = mean_missing_data.reset_index()
mean_missing_data.columns = ["Colonne", "Pourcentage_manquant", "Manquants < 90%"]g = sns.barplot(
x="Pourcentage_manquant",
y="Colonne",
data=mean_missing_data,
orient="h",
hue="Manquants < 90%",
palette="pastel",
)
# Ajoute une ligne verticale à 90%
g.axvline(x=90, color="red", linestyle="--", linewidth=1)
plt.yticks(fontsize=6)
# change scale of x axis to show 90%
plt.xticks([i for i in range(0, 101, 10)])
plt.ylabel("Nom des colonnes")
plt.xlabel("Taux de valeurs manquantes")
g.set_title("Répartition des valeurs manquantes dans EdStatsData")
#g.figure.savefig("images/manquants.png")([0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
62,
63,
64,
65,
66,
67,
68,
69],
[Text(0, 0, 'Country Name'),
Text(0, 1, 'Country Code'),
Text(0, 2, 'Indicator Name'),
Text(0, 3, 'Indicator Code'),
Text(0, 4, '1970'),
Text(0, 5, '1971'),
Text(0, 6, '1972'),
Text(0, 7, '1973'),
Text(0, 8, '1974'),
Text(0, 9, '1975'),
Text(0, 10, '1976'),
Text(0, 11, '1977'),
Text(0, 12, '1978'),
Text(0, 13, '1979'),
Text(0, 14, '1980'),
Text(0, 15, '1981'),
Text(0, 16, '1982'),
Text(0, 17, '1983'),
Text(0, 18, '1984'),
Text(0, 19, '1985'),
Text(0, 20, '1986'),
Text(0, 21, '1987'),
Text(0, 22, '1988'),
Text(0, 23, '1989'),
Text(0, 24, '1990'),
Text(0, 25, '1991'),
Text(0, 26, '1992'),
Text(0, 27, '1993'),
Text(0, 28, '1994'),
Text(0, 29, '1995'),
Text(0, 30, '1996'),
Text(0, 31, '1997'),
Text(0, 32, '1998'),
Text(0, 33, '1999'),
Text(0, 34, '2000'),
Text(0, 35, '2001'),
Text(0, 36, '2002'),
Text(0, 37, '2003'),
Text(0, 38, '2004'),
Text(0, 39, '2005'),
Text(0, 40, '2006'),
Text(0, 41, '2007'),
Text(0, 42, '2008'),
Text(0, 43, '2009'),
Text(0, 44, '2010'),
Text(0, 45, '2011'),
Text(0, 46, '2012'),
Text(0, 47, '2013'),
Text(0, 48, '2014'),
Text(0, 49, '2015'),
Text(0, 50, '2016'),
Text(0, 51, '2017'),
Text(0, 52, '2020'),
Text(0, 53, '2025'),
Text(0, 54, '2030'),
Text(0, 55, '2035'),
Text(0, 56, '2040'),
Text(0, 57, '2045'),
Text(0, 58, '2050'),
Text(0, 59, '2055'),
Text(0, 60, '2060'),
Text(0, 61, '2065'),
Text(0, 62, '2070'),
Text(0, 63, '2075'),
Text(0, 64, '2080'),
Text(0, 65, '2085'),
Text(0, 66, '2090'),
Text(0, 67, '2095'),
Text(0, 68, '2100'),
Text(0, 69, 'Unnamed: 69')])
([<matplotlib.axis.XTick at 0x733a3254ee90>,
<matplotlib.axis.XTick at 0x733a349d96d0>,
<matplotlib.axis.XTick at 0x733a349d9e50>,
<matplotlib.axis.XTick at 0x733a349da5d0>,
<matplotlib.axis.XTick at 0x733a349dad50>,
<matplotlib.axis.XTick at 0x733a349db4d0>,
<matplotlib.axis.XTick at 0x733a349dbc50>,
<matplotlib.axis.XTick at 0x733a34b1d090>,
<matplotlib.axis.XTick at 0x733a34b1d450>,
<matplotlib.axis.XTick at 0x733a34b1dbd0>,
<matplotlib.axis.XTick at 0x733a34b1e350>],
[Text(0, 0, '0'),
Text(10, 0, '10'),
Text(20, 0, '20'),
Text(30, 0, '30'),
Text(40, 0, '40'),
Text(50, 0, '50'),
Text(60, 0, '60'),
Text(70, 0, '70'),
Text(80, 0, '80'),
Text(90, 0, '90'),
Text(100, 0, '100')])
Text(0, 0.5, 'Nom des colonnes')
Text(0.5, 0, 'Taux de valeurs manquantes')
Text(0.5, 1.0, 'Répartition des valeurs manquantes dans EdStatsData')
Je décide de conserver uniquement les colonnes où moins de 90 % des données sont manquantes. Les données après 2015 présentent un nombre de valeurs manquantes significatif. Pour la suite de mon analyse, je vais me concentrer sur les colonnes contenant des données valides entre 2000 et 2015.
# Suppression des années non représentatives
columns_to_drop = [str(year) for year in range(1970, 1999)]
columns_to_drop.extend(["2016", "2017"])
columns_to_drop.extend([str(year) for year in range(2020, 2101, 5)])
edStatsData_filtered = edStatsData.drop(columns=columns_to_drop)
show(edStatsData_filtered["Indicator Name"].drop_duplicates())| None | Indicator Name |
|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
# Affiche valeurs non manquantes par colonnes
msno.bar(
edStatsData_filtered,
figsize=figsize,
fontsize=6,
)
plt.title("Valeurs non manquantes dans EdStatsData")Text(0.5, 1.0, 'Valeurs non manquantes dans EdStatsData')
On commence par vérifier s’il y a des lignes sans aucune valeurs
# Sélectionner toutes les colonnes sauf les 3 premières qui contiennent Country Names, Country Codes et indicateurs
columns_to_check = edStatsData_filtered.loc[:, "2000":"2015"]
# Cherche les lignes sans manquants
empty_rows_mask = columns_to_check.isna().all(axis=1)
# Afficher le nombre de lignes à supprimer, c'est-à-dire celles qui ont toutes les valeurs manquantes dans les colonnes de 2000 à 2015
num_empty_rows = edStatsData_filtered[empty_rows_mask].shape[0]
display(Markdown(f"Nombre de lignes vides à supprimer: **{num_empty_rows}**"))Nombre de lignes vides à supprimer: 538817
# Supprimer ces lignes du DataFrame en utilisant le masque créée précédemment
display(Markdown(f"Nombre de lignes **avant** suppression des lignes vides: **{edStatsData_filtered.shape[0]}**"))
edStatsData_filtered = edStatsData_filtered.dropna(axis=0, how="all", subset=columns_to_check.columns)
display(Markdown(f"Nombre de lignes **après** suppression des lignes vides: **{edStatsData_filtered.shape[0]}**"))Nombre de lignes avant suppression des lignes vides: 886930
Nombre de lignes après suppression des lignes vides: 348113
# Affiche les lignes vides
msno.matrix(edStatsData_filtered)
plt.title("Matrice des lignes vides dans edStatsData_filtered")
#plt.savefig("images/lignes_vides.png")Text(0.5, 1.0, 'Matrice des lignes vides dans edStatsData_filtered')
J’ai supprimé un grand nombre de lignes mais comme on peut le voir sur le graphique ci-dessus, il reste encore beaucoup de lignes vides. J’arrête ici la suppression des lignes vides pour ne pas supprimer trop de données.
Country CodeMaintenant, je vérifie que les codes pays sont valides.
# Charge données alpha-3
ISO_3166 = pd.read_csv(
"https://raw.githubusercontent.com/lukes/ISO-3166-Countries-with-Regional-Codes/refs/heads/master/all/all.csv",
usecols=["name", "alpha-3"],
)Markdown(f"La liste officielle qui répertorie les codes pays du monde contient **{ISO_3166["alpha-3"].nunique()}** codes")La liste officielle qui répertorie les codes pays du monde contient 249 codes
nb_country_code = edStatsData_filtered["Country Code"].nunique()
Markdown(f"EdStatsData contient **{nb_country_code}** codes de pays")EdStatsData contient 242 codes de pays
On conserve uniquement les codes présent dans la liste alpha-3 de ISO_3166.
# Créer un masque pour identifier les pays valides selon ISO_3166
valid_countries = edStatsData_filtered["Country Code"].isin(ISO_3166["alpha-3"])# Identifier et afficher les pays supprimés
removed_countries = edStatsData_filtered[~valid_countries]
removed_countries_list = pd.Series(removed_countries["Country Name"].unique(), name="Country Name")
show(removed_countries_list, "Liste des 'Pays' supprimés")| Country Name |
|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
# Filtrer les données pour conserver uniquement les pays valides
edStatsData_filtered = edStatsData_filtered[valid_countries]
show(edStatsData_filtered)| None | Country Name | Country Code | Indicator Name | Indicator Code | 1999 | 2000 | 2001 | 2002 | 2003 | 2004 | 2007 | 2008 | 2009 | 2010 | 2011 | 2012 | 2013 | 2014 | 2015 | Unnamed: 69 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
Markdown(f"Nombres de pays après filtrage d'après la liste alpha-3: **{edStatsData_filtered['Country Name'].nunique()}**")Nombres de pays après filtrage d’après la liste alpha-3: 215
Indicator Name# Nombre total d'indicateurs avant filtrage
total_indicators_before_filter = edStatsData["Indicator Name"].nunique()
Markdown(f" Nombre indicateurs avant filtre années et filtre lignes vides: **{total_indicators_before_filter}**")Nombre indicateurs avant filtre années et filtre lignes vides: 3665
# Liste des noms d'indicateurs après filtrage
filtered_indicators = edStatsData_filtered["Indicator Name"].drop_duplicates()
display(Markdown(f"Nombre d'indicateurs disponibles après filtrage: **{filtered_indicators.nunique()}**"))Nombre d’indicateurs disponibles après filtrage: 3635
# Calcul et affichage du nombre d'indicateurs supprimés
removed_indicators_count = total_indicators_before_filter - filtered_indicators.nunique()
display(Markdown(f"Le premier filtrage a supprimé : **{removed_indicators_count}** indicateurs"))Le premier filtrage a supprimé : 30 indicateurs
show(filtered_indicators, "EdStatsData après suppression des lignes vides")| None | Indicator Name |
|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
Petit bilan sur le jeu de données avant de poursuivre:
display(Markdown("Nom du jeu de données: **edStatsData_filtered**"))
display(Markdown(f"Doublons: **{edStatsData_filtered.duplicated().sum()}**"))
display(Markdown(f"Pays: **{edStatsData_filtered['Country Name'].nunique()}**"))
display(Markdown(f"Nb indicateurs: **{edStatsData_filtered['Indicator Name'].nunique()}**"))Nom du jeu de données: edStatsData_filtered
Doublons: 0
Pays: 215
Nb indicateurs: 3635
Pour avoir plus d’informations sur les indicateurs, j’importe leurs descriptions présentes dans le fichier EdStatSeries
# Ajoute la description des indicateurs grâce aux informations de edStatsSeries
edStatsSeries = dfs["EdStatsSeries"][["Series Code", "Topic", "Short definition", "Long definition"]]
edStatsData_filtered = edStatsData_filtered.merge(
right=edStatsSeries,
left_on="Indicator Code",
right_on="Series Code",
how="left",
)
# Supprimer la colonne Series Code
edStatsData_filtered = edStatsData_filtered.drop(columns="Series Code")
show(edStatsData_filtered.head(50))| Country Name | Country Code | Indicator Name | Indicator Code | 1999 | 2000 | 2001 | 2002 | 2003 | 2004 | 2005 | 2006 | 2007 | 2008 | 2009 | 2010 | 2011 | 2012 | 2013 | 2014 | 2015 | Unnamed: 69 | Topic | Short definition | Long definition |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
Maintenant je veux savoir si les indicateurs sont représentatifs.
Pour cela j’utilise un tableau croisée dynamique:
Pour afficher une ligne par indicateur
Pour chaque années entre 2000 et 2015 ➡️ compte le nombre de valeurs non nul
Note
Un indicateur est représentatif lorsqu’il est disponible pour chaque pays.
Dans mon cas comme je considère 215 pays, je dois avoir \(n=215\) observations par indicateur par année.
Un indicateur est représentatif si il est disponible pour au moins 50% des pays.
# Récupérer les noms des colonnes des années et le nombre de pays
years_columns = [str(year) for year in range(2000, 2016)]
nb_country = edStatsData_filtered["Country Name"].nunique()# Pivot table pour compter les valeurs non nulles par indicateur
indicator_usage_counts = edStatsData_filtered.pivot_table(
values=years_columns,
index=["Indicator Name", "Topic"],
aggfunc="count",
observed=True,
)msno.matrix(indicator_usage_counts.replace(0, np.nan))
plt.title("Utilisation des indicateurs par pays et par année")Text(0.5, 1.0, 'Utilisation des indicateurs par pays et par année')
# Calcul de la valeur moyenne par indicateur au fil des ans
indicator_usage_counts["Moyenne"] = indicator_usage_counts.mean(axis=1, skipna=True)
# Trie par ordre décroissant Frequence de données
indicator_usage_counts["Frequence"] = indicator_usage_counts["Moyenne"] / nb_country
indicator_usage_counts = indicator_usage_counts.sort_values(
by="Frequence",
ascending=False,
)
# Afficher les résultats avec la moyenne
show(
indicator_usage_counts[["Moyenne", "Frequence"]],
"Distribution des données : moyenne et taux de valeurs manquantes par indicateur",
)| Indicator Name | Topic | Moyenne | Frequence |
|---|---|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
#Je conserve les indicateurs qui ont un taux de valeurs manquantes de moins de 50%
MIN_FREQUENCY_THRESHOLD = 0.5
filtered_indicators = indicator_usage_counts.query(f"`Frequence` >{MIN_FREQUENCY_THRESHOLD}")
show(filtered_indicators[["Moyenne", "Frequence"]], f"Indicateurs avec une fréquence >= {MIN_FREQUENCY_THRESHOLD}")| Indicator Name | Topic | Moyenne | Frequence |
|---|---|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
Markdown(f"Il reste **{filtered_indicators.index.nunique()}** indicateurs")Il reste 461 indicateurs
J’ai supprimé beaucoup d’indicateurs. Avant de faire un bilan, je reconstruis le jeu de données.
# Récupère la liste unique des indicateurs valides
valid_indicators = filtered_indicators.index.unique("Indicator Name")
# Filtre le DataFrame pour ne garder que les lignes avec des indicateurs valides
edStatsData_filtered = edStatsData_filtered[
edStatsData_filtered["Indicator Name"].isin(valid_indicators)
]Bilan:
display(Markdown("Nom du jeu de données: **edStatsData_filtered**"))
display(Markdown(f"Doublons: **{edStatsData_filtered.duplicated().sum()}**"))
display(Markdown(f"Pays: **{edStatsData_filtered['Country Name'].nunique()}**"))
display(Markdown(f"Nb indicateurs: **{edStatsData_filtered['Indicator Name'].nunique()}** "))Nom du jeu de données: edStatsData_filtered
Doublons: 0
Pays: 215
Nb indicateurs: 461
Note
J’ai supprimé plus de 3000 indicateurs. Je pourrais avoir un résultat différent en analysant que l’année 2015.
Mais pour le moment je veux voir l’évolution des indicateurs depuis 2000.
Quels sont les thèmes des indicateurs ? Est ce qu’il y en a qui ne correspondent pas à mon étude ?
show(edStatsData_filtered["Topic"].drop_duplicates(),"Thèmes des indicateurs")| None | Topic |
|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
Le thème des indicateurs ne me permet pas de filtrer les colonnes des indicateurs.
Je regarde plus en détaille les indicateurs et leurs définitions.
show(edStatsData_filtered[["Indicator Name", "Long definition"]].drop_duplicates())| None | Indicator Name | Long definition |
|---|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
Je décide de supprimer les indicateurs qui mesurent des informations sur les cycles scolaires avant le lycée.
Sauf si les formations proposées par l’entreprise sera du soutien scolaire, les niveaux d’études inférieurs me semblent pas pertinents.
# Définition des niveaux d'éducation à exclure
UNWANTED_INDICATORS = ["pre-primary", "primary", "lower secondary"]
# Création du pattern regex pour la recherche
# Le | agit comme un "OU" en regex, permettant de chercher n'importe lequel des termes
pattern = "|".join(UNWANTED_INDICATORS)
# Filtrage du DataFrame pour exclure les indicateurs contenant ces termes
# Le ~ inverse la condition (garde les lignes qui ne contiennent PAS ces mots)
# case=False rend la recherche insensible à la casse
# na=False traite les valeurs manquantes comme False plutôt que de les propager
edStatsData_filtered = edStatsData_filtered[
~edStatsData_filtered["Indicator Name"].str.contains(
pattern,
case=False,
na=False,
)
]Bilan
display(Markdown("Nom du jeu de données: **edStatsData_filtered**"))
display(Markdown(f"Doublons: **{edStatsData_filtered.duplicated().sum()}**"))
display(Markdown(f"Pays: **{edStatsData_filtered['Country Name'].nunique()}**"))
display(Markdown(f"Nb indicateurs: **{edStatsData_filtered['Indicator Name'].nunique()}** "))Nom du jeu de données: edStatsData_filtered
Doublons: 0
Pays: 215
Nb indicateurs: 300
L’opération m’a permis de supprimer plus de 100 d’indicateurs.
Pour étude est préliminaire, J’analyse globalement la population pour évaluer des potentiels clients. Je supprime les indicateurs qui représentent une partie de la population qui contiennent female ou male.
# Définition des termes à exclure (indicateurs de genre et de segmentation de population)
POPULATION_FILTERS = [
"female",
"male",
]
# Création du pattern de recherche
filter_pattern = "|".join(POPULATION_FILTERS)
# Création d'un nouveau DataFrame excluant les indicateurs spécifiques à certaines populations
edStatsData_filtered = edStatsData_filtered[
~edStatsData_filtered["Indicator Name"].str.contains(
filter_pattern,
case=False,
na=False,
)
]Bilan
display(Markdown("Nom du jeu de données: **edStatsData_filtered**"))
display(Markdown(f"Doublons: **{edStatsData_filtered.duplicated().sum()}**"))
display(Markdown(f"Pays: **{edStatsData_filtered['Country Name'].nunique()}**"))
display(Markdown(f"Nb indicateurs: **{edStatsData_filtered['Indicator Name'].nunique()}** "))Nom du jeu de données: edStatsData_filtered
Doublons: 0
Pays: 215
Nb indicateurs: 124
Maintenant que j’ai supprimé la plupart des indicateurs peu pertinents pour mon étude préliminaire, je sélectionne les indicateurs qui me semblent les plus intéressants. #### Selection des indicateurs:
Je sélectionne 6 indicateurs qui couvrent des dimensions essentielles tel que:
Enrolment in upper secondary education, both sexes (number)Population, ages 15-64 (% of total)Population, totalInternet users (per 100 people)GNI per capita, PPP (current international $)Government expenditure on education as % of GDP (%)# Définition des termes à exclure (indicateurs de genre et de segmentation de population)
INCLUDED_INDICATORS = [
# Etudes académiques
"Enrolment in upper secondary education, both sexes (number)",
# Répartition de la population
"Population, ages 15-64 (% of total)",
"Population, total",
# Accès technologique
"Internet users (per 100 people)",
# Capacité financière des habitants
"GNI per capita, PPP (current international $)",
# Investissement public dans l'éducation
"Government expenditure on education as % of GDP (%)",
]# Création d'un nouveau DataFrame excluant les indicateurs spécifiques à certaines populations
edStatsData_final = edStatsData_filtered.query(f"`Indicator Name` in {INCLUDED_INDICATORS}").copy()Bilan
display(Markdown("Nom du jeu de données: **edStatsData_final**"))
display(Markdown(f"Doublons: **{edStatsData_final.duplicated().sum()}**"))
display(Markdown(f"Pays: **{edStatsData_final['Country Name'].nunique()}**"))
display(Markdown(f"Nb indicateurs: **{edStatsData_final['Indicator Name'].nunique()}** "))Nom du jeu de données: edStatsData_final
Doublons: 0
Pays: 215
Nb indicateurs: 6
J’ai extrait 5 indicateurs pour 211 pays dans mon jeu de données.
Je vais créer un scoring basé sur ces indicateurs.
Pour cela, j’ai besoin d’établir un classement pour chacun de mes indicateurs par pays et par an.
Mais d’abord je vais récupérer les régions du monde. Qui me permettra de visualiser la répartition de ces pays dans le monde.
Je charge le fichier puis le fusionne avec mon jeu de données final.
edStatsCountry=dfs["EdStatsCountry"]
show(edStatsCountry["Region"].drop_duplicates())| None | Region |
|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
edStatsData_final = edStatsData_final.merge(
edStatsCountry[["Country Code", "Region"]],
on="Country Code",
how="left",
)
show(edStatsData_final)| Country Name | Country Code | Indicator Name | Indicator Code | 1999 | 2000 | 2001 | 2002 | 2003 | 2004 | 2005 | 2006 | 2007 | 2008 | 2009 | 2010 | 2011 | 2012 | 2013 | 2014 | 2015 | Unnamed: 69 | Topic | Short definition | Long definition | Region |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
Pour sélectionner les pays, j’ai besoin d’analyser la valeurs des indicateurs par pays par année. Actuellement j’ai les années entre 2000 et 2015. L’échelle de temps est trop importante. Je vais étudier les années 2010 et 2015. En cas de de valeurs manquantes je conserve la dernière valeur connue.
# J'utilise la méthode ffill pour remplacer les valeurs manquantes par la dernière valeur connue à partir de 2010
years_2010_2015 = [str(year) for year in range(2010, 2016)]
edStatsData_final["Last_Value"] = edStatsData_final[years_2010_2015].ffill(axis=1)["2015"]
show(edStatsData_final)| Country Name | Country Code | Indicator Name | Indicator Code | 1999 | 2000 | 2001 | 2002 | 2003 | 2004 | 2005 | 2006 | 2007 | 2008 | 2009 | 2010 | 2011 | 2012 | 2013 | 2014 | 2015 | Unnamed: 69 | Topic | Short definition | Long definition | Region | Last_Value |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
Maintenant que j’ai la dernière valeur connue pour chaque indicateur, Je créer un nouveau dataframe avec les indicateurs et les pays.
columns_to_keep = ["Country Name", "Region", "Indicator Name", "Last_Value"]
edStatsData_final_Pivot = edStatsData_final[columns_to_keep].pivot_table(
index=["Country Name", "Region"],
columns="Indicator Name",
values="Last_Value",
observed=True,
)
edStatsData_final_Pivot = edStatsData_final_Pivot.reset_index()
show(edStatsData_final_Pivot, "Valeurs des indicateurs par pays")| Country Name | Region | Enrolment in upper secondary education, both sexes (number) | GNI per capita, PPP (current international $) | Government expenditure on education as % of GDP (%) | Internet users (per 100 people) | Population, ages 15-64 (% of total) | Population, total |
|---|---|---|---|---|---|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
Je commence par vérifier s’il y a des valeurs manquantes
# Je crée un graphique pour visualiser les valeurs manquantes
msno.matrix(
edStatsData_final_Pivot,
filter="top",
sort="descending",
figsize=(12, 10),
color=(0.49, 0.69, 0.90),
)
plt.title(
"Valeurs manquantes par pays avant filtrage",
fontsize=16,
)
plt.show()Text(0.5, 1.0, 'Valeurs manquantes par pays avant filtrage')
Je supprime les pays qui ont des valeurs manquantes dans la liste des indicateurs.
# Filtrer le noms des pays avec suffisamment de données non manquantes
valid_countries = edStatsData_final_Pivot.dropna(thresh=4)["Country Name"].to_list()
# J'obtient la liste des pays à supprimer
countries_to_remove = edStatsData_final_Pivot.query(f"`Country Name` not in {valid_countries}")["Country Name"]
show(
countries_to_remove,
"Pays qui ont des valeurs manquantes dans la liste des indicateurs",
)| None | Country Name |
|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
edStatsData_final_Pivot = edStatsData_final_Pivot.query(f"`Country Name` not in {countries_to_remove.to_list()}")msno.matrix(
edStatsData_final_Pivot,
filter="top",
sort="descending",
figsize=(12, 10),
color=(0.49, 0.69, 0.90),
)
plt.title("Valeurs manquantes par pays après filtrage", fontsize=16)
plt.show()Text(0.5, 1.0, 'Valeurs manquantes par pays après filtrage')
# Représentation des histogrammes pour chacune des variables.
nrows = len(INCLUDED_INDICATORS) // 2
ncols = 2
fig, axes = plt.subplots(nrows, ncols, figsize=(20, 20))
sns.set(font_scale=1.25)
for indicator in INCLUDED_INDICATORS:
i = INCLUDED_INDICATORS.index(indicator)
sns.histplot(
ax=axes[i // ncols, i % ncols],
data=edStatsData_final_Pivot,
x=indicator,
)
fig.suptitle("Répartition empirique des indicateurs", fontsize=25)
plt.show()
#fig.savefig("images/distribution_indicateurs.png")Text(0.5, 0.98, 'Répartition empirique des indicateurs')
Certains indicateurs ont des écarts d’échelle très grands Je vais utiliser une transformation logarithmique pour les rendre plus lisible. Il s’agit de : Enrolment in upper secondary education, both sexes (number), Population, total, GNI per capita, PPP (current international $).
INDICATORS_TO_LOG = [
"Enrolment in upper secondary education, both sexes (number)",
"Population, total",
"GNI per capita, PPP (current international $)",
]
# Transformation des données en log (naturel, népérien) pour s'approcher d'une distribution suivant une loi normal.
for indicator in INDICATORS_TO_LOG:
edStatsData_final_Pivot[f"log_{indicator}"] = np.log(edStatsData_final_Pivot[indicator])
# Suppression des indicateurs d'origine
edStatsData_final_Pivot = edStatsData_final_Pivot.drop(columns=INDICATORS_TO_LOG)
show(edStatsData_final_Pivot, "Valeurs des indicateurs après transformation logarithmique")| None | Country Name | Region | Government expenditure on education as % of GDP (%) | Internet users (per 100 people) | Population, ages 15-64 (% of total) | log_Enrolment in upper secondary education, both sexes (number) | log_Population, total | log_GNI per capita, PPP (current international $) |
|---|---|---|---|---|---|---|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
Dans la liste des indicateurs, je remplace les indicateurs par leurs équivalents transformés logarithmiques.
INCLUDED_INDICATORS_WITH_LOG = [
# Etudes académiques
"log_Enrolment in upper secondary education, both sexes (number)",
# Répartition de la population
"Population, ages 15-64 (% of total)",
"log_Population, total",
# Accès technologique
"Internet users (per 100 people)",
# Capacité financière des habitants
"log_GNI per capita, PPP (current international $)",
# Investissement public dans l'éducation
"Government expenditure on education as % of GDP (%)",
]Je retrace les graphiques de répartition empirique des indicateurs.
# Représentation des histogrammes pour chacune des variables.
fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=(20, 20))
sns.set(font_scale=1.25)
for indicator in INCLUDED_INDICATORS_WITH_LOG:
i = INCLUDED_INDICATORS_WITH_LOG.index(indicator)
sns.histplot(
ax=axes[i // ncols, i % ncols],
data=edStatsData_final_Pivot,
x=indicator,
)
fig.suptitle("Répartition empirique des indicateurs", fontsize=25)
plt.show()
#fig.savefig("images/distribution_indicateurs_log.png")Text(0.5, 0.98, 'Répartition empirique des indicateurs')
Tous les indicateurs suivent une loi normale. Je peux maintenant analyser la relation entre les indicateurs.
Par pair d’indicateurs je vais tracer des nuages de points. L’apparition de lignes diagonales dans les nuages de points indique la possibilités d’une corrélation entre les indicateurs. Une fonction pratique est pairplot de la librairie seaborn.
# Analyse de la relation entre les indicateurs
g = sns.pairplot(
data=edStatsData_final_Pivot,
vars=INCLUDED_INDICATORS_WITH_LOG,
corner=True,
kind="reg",
plot_kws={"line_kws": {"color": "red"}},
)
g.fig.suptitle("Analyse de la relation entre les indicateurs", fontsize=25)
for ax in g.axes.flatten():
if ax:
# rotate x axis labels
ax.set_xlabel(ax.get_xlabel(), rotation=45)
# rotate y axis labels
ax.set_ylabel(ax.get_ylabel(), rotation=0)
# set y labels alignment
ax.yaxis.get_label().set_horizontalalignment("right")
# set x labels alignment
ax.xaxis.get_label().set_horizontalalignment("right")
plt.show()
#g.savefig("images/pairplot_indicateurs.png")Text(0.5, 0.98, 'Analyse de la relation entre les indicateurs')
Text(0.5, 1205.3166666666666, '')
Text(126.12500000000001, 0.5, 'log_Enrolment in upper secondary education, both sexes (number)')
Text(0.5, 979.3666666666666, 'log_Enrolment in upper secondary education, both sexes (number)')
Text(60.274999999999984, 0.5, 'Population, ages 15-64 (% of total)')
Text(0.5, 979.3666666666666, '')
Text(365.98095238095243, 0.5, '')
Text(0.5, 753.4166666666666, 'log_Enrolment in upper secondary education, both sexes (number)')
Text(60.274999999999984, 0.5, 'log_Population, total')
Text(0.5, 753.4166666666666, 'Population, ages 15-64 (% of total)')
Text(316.7145833333334, 0.5, 'log_Population, total')
Text(0.5, 753.4166666666666, '')
Text(557.2952380952381, 0.5, '')
Text(0.5, 527.4666666666666, 'log_Enrolment in upper secondary education, both sexes (number)')
Text(50.024999999999984, 0.5, 'Internet users (per 100 people)')
Text(0.5, 527.4666666666666, 'Population, ages 15-64 (% of total)')
Text(316.7145833333334, 0.5, 'Internet users (per 100 people)')
Text(0.5, 527.4666666666666, 'log_Population, total')
Text(539.9875, 0.5, 'Internet users (per 100 people)')
Text(0.5, 527.4666666666666, '')
Text(748.6095238095238, 0.5, '')
Text(0.5, 301.5166666666667, 'log_Enrolment in upper secondary education, both sexes (number)')
Text(60.274999999999984, 0.5, 'log_GNI per capita, PPP (current international $)')
Text(0.5, 301.5166666666667, 'Population, ages 15-64 (% of total)')
Text(316.7145833333334, 0.5, 'log_GNI per capita, PPP (current international $)')
Text(0.5, 301.5166666666667, 'log_Population, total')
Text(539.9875, 0.5, 'log_GNI per capita, PPP (current international $)')
Text(0.5, 301.5166666666667, 'Internet users (per 100 people)')
Text(763.2604166666666, 0.5, 'log_GNI per capita, PPP (current international $)')
Text(0.5, 301.5166666666667, '')
Text(939.9238095238095, 0.5, '')
Text(0.5, 44.90000000000001, 'log_Enrolment in upper secondary education, both sexes (number)')
Text(60.274999999999984, 0.5, 'Government expenditure on education as % of GDP (%)')
Text(0.5, 44.90000000000001, 'Population, ages 15-64 (% of total)')
Text(316.7145833333334, 0.5, 'Government expenditure on education as % of GDP (%)')
Text(0.5, 44.90000000000001, 'log_Population, total')
Text(539.9875, 0.5, 'Government expenditure on education as % of GDP (%)')
Text(0.5, 44.90000000000001, 'Internet users (per 100 people)')
Text(763.2604166666666, 0.5, 'Government expenditure on education as % of GDP (%)')
Text(0.5, 44.90000000000001, 'log_GNI per capita, PPP (current international $)')
Text(986.5333333333332, 0.5, 'Government expenditure on education as % of GDP (%)')
Text(0.5, 44.90000000000001, 'Government expenditure on education as % of GDP (%)')
Text(1131.2380952380954, 0.5, '')
D’après le graphique ci-dessus, une corrélation semble exister entre les pairs d’indicateurs suivants:
log_Enrolment in upper secondary education, both sexes (number) et log_Population, total
Internet users (per 100 people) et log_GNI per capita, PPP (current international $)
Je vérifie mon hypothèse avec une matrice de corrélation de Pearson.
# Calcul de la matrice de corrélation
correlation_matrix = edStatsData_final_Pivot[INCLUDED_INDICATORS_WITH_LOG].corr()
# Mask du triangle supérieur de la matrice
mask = np.triu(np.ones_like(correlation_matrix, dtype=bool))
# Affichage de la matrice de corrélation
heat_map = sns.heatmap(
correlation_matrix,
annot=True,
fmt=".2f",
mask=mask,
cmap="vlag",
vmin=-1,
vmax=1,
linewidths=0.5,
cbar_kws={"shrink": 0.5},
)
# Rotation des labels des axes
heat_map.set_xticklabels(
heat_map.get_xticklabels(),
rotation=45,
horizontalalignment="right",
)
heat_map.set(
xlabel=None,
ylabel=None,
)
# Titre et dimensions de la figure
heat_map.set_title(
"Matrice de corrélation entre les indicateurs",
fontsize=30,
)
plt.figure(figsize=(15, 15))
plt.show()[Text(0.5, 0, 'log_Enrolment in upper secondary education, both sexes (number)'),
Text(1.5, 0, 'Population, ages 15-64 (% of total)'),
Text(2.5, 0, 'log_Population, total'),
Text(3.5, 0, 'Internet users (per 100 people)'),
Text(4.5, 0, 'log_GNI per capita, PPP (current international $)'),
Text(5.5, 0, 'Government expenditure on education as % of GDP (%)')]
Text(0.5, 1.0, 'Matrice de corrélation entre les indicateurs')
<Figure size 1440x1440 with 0 Axes>
La matrice de corrélation confirme mon hypothèse, les pairs d’indicateurs suivants sont corrélés:
log_Enrolment in upper secondary education, both sexes (number) et log_Population, total
Internet users (per 100 people) et log_GNI per capita, PPP (current international $)
Leurs coefficients de corrélation sont respectivement de 0.97 et 0.90. Soit très proche de 1. Cela signifie que :
plus la population est importante, plus le nombre d’étudiants est important.
plus l’investissement dans l’éducation est important, plus l’accès à l’internet est important.
Rien de surprenant, je vais définir mon scoring en fonction de ces indicateurs. Plus hautes sont les valeurs, plus le pays est intéressant.
Avant de définir mon scoring, je vais m’assurer que je n’ai pas de valeurs aberrantes en affichant les boxplots de chaque indicateur.
# Affichage des grandeurs statistiques par indicateurs
show(edStatsData_final_Pivot.describe())| None | Government expenditure on education as % of GDP (%) | Internet users (per 100 people) | Population, ages 15-64 (% of total) | log_Enrolment in upper secondary education, both sexes (number) | log_Population, total | log_GNI per capita, PPP (current international $) |
|---|---|---|---|---|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
# Affichage des boxplots de chaque indicateur
ncols = len(INCLUDED_INDICATORS_WITH_LOG) // 2
nrows = 2
fig, ax = plt.subplots(nrows=nrows, ncols=ncols, figsize=(20, 20))
fig.suptitle(
"Répartition des valeurs par indicateurs",
fontsize=25,
y=1.01,
)
subplots = {}
# pour chaque graphe indicateurs
for indicator in INCLUDED_INDICATORS_WITH_LOG:
i = INCLUDED_INDICATORS_WITH_LOG.index(indicator)
n_ax = ax[i // ncols, i % ncols]
subplot = sns.violinplot(
ax=n_ax,
data=edStatsData_final_Pivot,
orient="y",
y=indicator,
fill=False,
inner_kws=dict(box_width=25, whis_width=2, color="blue"),
)
subplot.set_xlabel(None)
subplot.set_title(indicator, fontsize=20)
subplot.set_ylabel(None)
subplot.yaxis.set_tick_params()
# Save each subplot in a dictionary
subplots[indicator] = subplot
# Pour chaque indicateur, je définis la valeur limite pour filtrer les valeurs
INCLUDED_INDICATORS_QUANTILE = {
"Internet users (per 100 people)": 0.5,
"Government expenditure on education as % of GDP (%)": None,
"log_Enrolment in upper secondary education, both sexes (number)": 0.25,
"log_GNI per capita, PPP (current international $)": 0.25,
"log_Population, total": 0.25,
"Population, ages 15-64 (% of total)": 0.25,
}
# Pour chaque graphique j'ajoute une ligne rouge
for indicator, quantile in INCLUDED_INDICATORS_QUANTILE.items():
if quantile is not None:
subplots[indicator].axhline(
y=edStatsData_final_Pivot[indicator].quantile(quantile),
color="red",
linestyle="--",
label=f"Quantile {quantile}",
)
fig.tight_layout()
plt.show()
#fig.savefig("images/boxplots_indicateurs.png")Text(0.5, 1.01, 'Répartition des valeurs par indicateurs')
Text(0.5, 0, '')
Text(0.5, 1.0, 'log_Enrolment in upper secondary education, both sexes (number)')
Text(0, 0.5, '')
Text(0.5, 0, '')
Text(0.5, 1.0, 'Population, ages 15-64 (% of total)')
Text(0, 0.5, '')
Text(0.5, 0, '')
Text(0.5, 1.0, 'log_Population, total')
Text(0, 0.5, '')
Text(0.5, 0, '')
Text(0.5, 1.0, 'Internet users (per 100 people)')
Text(0, 0.5, '')
Text(0.5, 0, '')
Text(0.5, 1.0, 'log_GNI per capita, PPP (current international $)')
Text(0, 0.5, '')
Text(0.5, 0, '')
Text(0.5, 1.0, 'Government expenditure on education as % of GDP (%)')
Text(0, 0.5, '')
Le graphique ci-dessus permet de visualiser les modes de distribution des indicateurs ainsi que les répartitions interquartiles. voici le filtre que je vais appliquer:
Internet users (per 100 people): critère de sélection le plus important, je conserve les valeurs supérieures à la médiane.
Government expenditure on education as % of GDP (%): Je conserve toutes les valeurs car elles assez sont rapprochées.
Pour les autres indicateurs, je conserve les valeurs supérieures au premier quartile.
# Créer les conditions avec des masques booléens
CONDITIONS = []
# Pour chaque indicateur, je conserve les valeurs supérieures au quantile défini
# Pour cela je concatene les conditions avec query
for indicator, quantile in INCLUDED_INDICATORS_QUANTILE.items():
escaped_indicator = f"`{indicator}`"
if quantile is not None:
Q_value = edStatsData_final_Pivot[indicator].quantile(quantile)
CONDITIONS.append(f"{escaped_indicator} > {Q_value}")
else:
# Pour Government expenditure on education as % of GDP (%) je conserver toutes les valeurs
Q_value = 0
CONDITIONS.append(f"{escaped_indicator} > {Q_value}")
CONDITIONS = " & ".join(CONDITIONS)# Appliquer toutes les conditions
edStatsData_final_Pivot = edStatsData_final_Pivot.query(CONDITIONS)show(edStatsData_final_Pivot, "Jeu de données filtré")
Markdown(f"Après filtrage du jeu de données, il reste: **{edStatsData_final_Pivot['Country Name'].nunique()}** pays")| None | Country Name | Region | Government expenditure on education as % of GDP (%) | Internet users (per 100 people) | Population, ages 15-64 (% of total) | log_Enrolment in upper secondary education, both sexes (number) | log_Population, total | log_GNI per capita, PPP (current international $) |
|---|---|---|---|---|---|---|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
Après filtrage du jeu de données, il reste: 56 pays
from sklearn.preprocessing import MinMaxScaler
# Initialisation du scaler pour normaliser les données entre 0 et 1
normalizer = MinMaxScaler()
# Préparation des données pour la normalisation
data_to_normalize = edStatsData_final_Pivot.drop(columns=["Country Name", "Region"])
column_names = data_to_normalize.columns
# Application de la normalisation
normalized_values = normalizer.fit_transform(data_to_normalize)
# Création du DataFrame avec les valeurs normalisées
normalized_indicators = pd.DataFrame(
normalized_values,
index=edStatsData_final_Pivot.index,
columns=column_names,
)
# Ajout de la colonne des noms de pays et de la colonne des régions
normalized_indicators["Country Name"] = edStatsData_final_Pivot["Country Name"]
normalized_indicators["Region"] = edStatsData_final_Pivot["Region"]
# Calcul du score global (moyenne des indicateurs)
normalized_indicators["Global_Score"] = normalized_indicators[column_names].mean(axis=1)
# Affichage des 5 meilleurs pays selon le score global
show(normalized_indicators.sort_values("Global_Score", ascending=False).head(5), "Top 5 des pays selon le score global")| None | Government expenditure on education as % of GDP (%) | Internet users (per 100 people) | Population, ages 15-64 (% of total) | log_Enrolment in upper secondary education, both sexes (number) | log_Population, total | log_GNI per capita, PPP (current international $) | Country Name | Region | Global_Score |
|---|---|---|---|---|---|---|---|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
Je souhaite afficher les régions qui ont le plus de pays à fort potentiel.
Je commence par visualiser la répartition par région par indicateur.
# Afficher les boxplots par région par indicateur
fig, ax = plt.subplots(
nrows=nrows,
ncols=ncols,
figsize=(30, 15),
sharex=False,
sharey="row",
)
plt.suptitle(
"Répartition par région par indicateur",
fontsize=25,
y=1.01,
)
for indicator in INCLUDED_INDICATORS_WITH_LOG:
i = INCLUDED_INDICATORS_WITH_LOG.index(indicator)
n_ax = ax[i // ncols, i % ncols]
subplot = sns.boxplot(
ax=n_ax,
data=normalized_indicators,
x=indicator,
y="Region",
orient="h",
)
subplot.set_xlabel(None)
subplot.set_ylabel(None)
subplot.set_title(indicator, fontsize=20)
subplot.yaxis.set_tick_params()
fig.tight_layout()
plt.show()
#fig.savefig("images/boxplots_indicateurs_par_region.png")Text(0.5, 1.01, 'Répartition par région par indicateur')
Text(0.5, 0, '')
Text(0, 0.5, '')
Text(0.5, 1.0, 'log_Enrolment in upper secondary education, both sexes (number)')
Text(0.5, 0, '')
Text(0, 0.5, '')
Text(0.5, 1.0, 'Population, ages 15-64 (% of total)')
Text(0.5, 0, '')
Text(0, 0.5, '')
Text(0.5, 1.0, 'log_Population, total')
Text(0.5, 0, '')
Text(0, 0.5, '')
Text(0.5, 1.0, 'Internet users (per 100 people)')
Text(0.5, 0, '')
Text(0, 0.5, '')
Text(0.5, 1.0, 'log_GNI per capita, PPP (current international $)')
Text(0.5, 0, '')
Text(0, 0.5, '')
Text(0.5, 1.0, 'Government expenditure on education as % of GDP (%)')
Le box plot correspond à une seule valeur. Il n’y a qu’un seul pays dans la région “Sub-Saharan Africa” pour certains indicateurs.
# Afficher les régions qui ont le plus de pays à fort potentiel
region_scores = normalized_indicators.groupby("Region")["Global_Score"].agg(["mean", "count"]).reset_index()
region_scores = region_scores.sort_values("count", ascending=False)
# Créer le graphique à barres
bars = region_scores.plot(
kind="bar",
y="count",
x="Region",
figsize=(12, 6),
xlabel="Région",
ylabel="Nombre de pays",
title="Nombre de pays par région en 2015",
color="#7eb0d5",
)
# Ajouter le nombre de pays par région au-dessus de chaque barre
for i, bar in enumerate(region_scores["count"]):
plt.text(i, bar, f"{int(bar)}", ha="center", va="bottom")
plt.xticks(rotation=45, ha="right")
plt.show()Text(0, 34, '34')
Text(1, 9, '9')
Text(2, 6, '6')
Text(3, 4, '4')
Text(4, 2, '2')
Text(5, 1, '1')
(array([0, 1, 2, 3, 4, 5]),
[Text(0, 0, 'Europe & Central Asia'),
Text(1, 0, 'Latin America & Caribbean'),
Text(2, 0, 'East Asia & Pacific'),
Text(3, 0, 'Middle East & North Africa'),
Text(4, 0, 'North America'),
Text(5, 0, 'Sub-Saharan Africa')])
# Afficher les régions qui ont le plus de pays à fort potentiel
region_scores = normalized_indicators.groupby("Region")["Global_Score"].agg(["mean", "count"]).reset_index()
region_scores = region_scores.sort_values("mean", ascending=False)
# Créer le graphique à barres
bars = region_scores.plot(
kind="bar",
y="mean",
x="Region",
figsize=(12, 6),
xlabel="Région",
ylabel="Score moyen",
title="Score moyen par région en 2015",
color="#7eb0d5",
)
# Ajouter le nombre de pays par région au-dessus de chaque barre
for i, bar in enumerate(region_scores["mean"]):
plt.text(i, bar, f"{bar:.2f}", ha="center", va="bottom")
plt.xticks(rotation=45, ha="right")
plt.show()Text(0, 0.6691783145718233, '0.67')
Text(1, 0.5684828107178999, '0.57')
Text(2, 0.4625913543275, '0.46')
Text(3, 0.4432887107872709, '0.44')
Text(4, 0.4178827713432749, '0.42')
Text(5, 0.4161498717324793, '0.42')
(array([0, 1, 2, 3, 4, 5]),
[Text(0, 0, 'North America'),
Text(1, 0, 'East Asia & Pacific'),
Text(2, 0, 'Europe & Central Asia'),
Text(3, 0, 'Sub-Saharan Africa'),
Text(4, 0, 'Latin America & Caribbean'),
Text(5, 0, 'Middle East & North Africa')])
Les pays d’Europe centrale et Orientale ont le plus de pays à fort potentiel.
# Enregistre les 5 pays les plus performants dans un dataframe
top_5 = normalized_indicators.reset_index().nlargest(5, "Global_Score")
# Je vais utiliser un graphique en barres
top_5.plot(
kind="bar",
y="Global_Score",
x="Country Name",
figsize=(12, 6),
xlabel="Pays",
ylabel="Score global",
title="Top 5 des pays selon le score global",
color="#7eb0d5",
)
plt.xticks(rotation=45, ha="right")
# add values on top of the bars
for i, v in enumerate(top_5["Global_Score"]):
plt.text(i, v, f"{v:.2f}", ha="center", va="bottom")
plt.show()(array([0, 1, 2, 3, 4]),
[Text(0, 0, 'United States'),
Text(1, 0, 'Korea, Rep.'),
Text(2, 0, 'United Kingdom'),
Text(3, 0, 'Germany'),
Text(4, 0, 'Canada')])
Text(0, 0.7095048554922959, '0.71')
Text(1, 0.6836489435332044, '0.68')
Text(2, 0.659952389028153, '0.66')
Text(3, 0.6432130872262989, '0.64')
Text(4, 0.6288517736513507, '0.63')
show(top_5, "Top 5 des pays selon le score global")| None | index | Government expenditure on education as % of GDP (%) | Internet users (per 100 people) | Population, ages 15-64 (% of total) | log_Enrolment in upper secondary education, both sexes (number) | log_Population, total | log_GNI per capita, PPP (current international $) | Country Name | Region | Global_Score |
|---|---|---|---|---|---|---|---|---|---|---|
| Loading ITables v2.2.4 from the internet... (need help?) |
Pour chacun des 5 pays, j’affiche le score obtenu pour chaque indicateur.
from textwrap import wrap
# Créer un DataFrame au format long pour faciliter le plotting
top_5_long = top_5.melt(
id_vars=["Country Name"],
value_vars=INCLUDED_INDICATORS_WITH_LOG, # Utiliser uniquement les indicateurs
var_name="Indicator",
value_name="Score",
)
for indicator in top_5_long["Indicator"].unique():
top_5_long.loc[top_5_long["Indicator"] == indicator, "Indicator"] = "\n".join(wrap(indicator, 40))
g = sns.FacetGrid(
top_5_long,
col="Country Name",
height=3.5,
aspect=1,
col_wrap=3,
margin_titles=True,
hue="Indicator",
palette="Set2",
)
g.map(sns.barplot, "Score", "Indicator")
g.set_titles(col_template="{col_name}")
g.fig.suptitle("Score par indicateur du top 5 des pays", fontsize=20)
g.tight_layout()
plt.show()/home/laguill/Documents/01-Etudes/OpenClassrooms/P2_Analysez-donnees-systemes-educatifs/.pixi/envs/default/lib/python3.13/site-packages/seaborn/axisgrid.py:718: UserWarning: Using the barplot function without specifying `order` is likely to produce an incorrect plot.
warnings.warn(warning)
Text(0.5, 0.98, 'Score par indicateur du top 5 des pays')